#include "uesdk/utils.h"
#include "uesdk/http/http_connect.h"

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/bind/placeholders.hpp>


namespace uesdk{
namespace http{

HttpResult::HttpResult(Error result, HttpResponse::ptr response,const std::string& error)
    :m_result(result)
    ,m_resonse(response)
    ,m_error(error){
}

std::string HttpResult::toString() const {
    std::stringstream ss;
    ss << "[HttpResult result = " << (int)m_result
       << " error=" << m_error
       << " response=" << (m_resonse ? m_resonse->toString() : "nullptr")
       << "]";
    return ss.str();
}


HttpResult::ptr HttpConnection::DoGet(const std::string& url, uint64_t timeout_ms
                        , const std::map<std::string, std::string>& header
                        , const std::string& body){
    return DoRequest(HttpMethod::GET,url,timeout_ms,header,body);
}

HttpResult::ptr HttpConnection::DoPost(const std::string& url, uint64_t timeout_ms
                        , const std::map<std::string, std::string>& header
                        , const std::string& body){
    return DoRequest(HttpMethod::POST,url,timeout_ms,header,body);
}

HttpResult::ptr HttpConnection::DoRequest(HttpMethod method, const std::string& url, uint64_t timeout_ms
                        , const std::map<std::string, std::string>& header
                        , const std::string& body){
    HttpRequest::ptr req = boost::make_shared<HttpRequest>();
    req->setMethod(method);
    req->setBody(body);
    for(std::map<std::string, std::string>::const_iterator i = header.begin();i != header.end();++i){
        req->setHeader(i->first,i->second);
    }
    return DoRequest(req,url,timeout_ms);

}

static size_t body_write_cb(char *data, size_t size, size_t nmemb, void *userp)
{
    HttpResponse* parser = static_cast<HttpResponse*>(userp);
    size_t realsize = size * nmemb;
    std::string result = parser->getBody() + std::string(data,realsize);
    parser->setBody(result);
    return realsize;
}

static size_t header_write_cb(char *data, size_t size, size_t nmemb, void *userp)
{
    HttpResponse* parser = static_cast<HttpResponse*>(userp);
    size_t realsize = size * nmemb;
    std::string result = std::string(data,realsize);
    std::vector<std::string> vec_str = StringUtil::split(result,":",2);
    if(vec_str.size()!=2){
        return realsize;
    }
    std::string key = StringUtil::trim(vec_str[0]);
    std::string value = StringUtil::trim(vec_str[1]);
    parser->setHeader(key,value);
    return realsize;
}

HttpResult::ptr HttpConnection::DoRequest(HttpRequest::ptr req, const std::string& url, uint64_t timeout_ms){
    if(!m_curl){
        m_close = true;
		return HttpResult::ptr(new HttpResult(HttpResult::Error::POOL_INVALID_CONNECTION,boost::shared_ptr<HttpResponse>(),"invalid connect"));
    }
    HttpResponse::ptr rsp = boost::make_shared<HttpResponse>();
    curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, body_write_cb);
    curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, (void *)rsp.get());
    curl_easy_setopt(m_curl, CURLOPT_HEADERFUNCTION, header_write_cb);
    curl_easy_setopt(m_curl, CURLOPT_HEADERDATA, (void *)rsp.get());
    curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1L);
    curl_easy_setopt(m_curl, CURLOPT_CUSTOMREQUEST, HttpMethodToString(req->getMethod()));
    curl_slist * headers = NULL;
	for(HttpRequest::MapType::const_iterator i = req->getHeader().begin();
			i != req->getHeader().end(); ++i){
        std::string value = i->first + ": " + i->second;
        headers = curl_slist_append(headers,value.c_str());
    }
    curl_easy_setopt(m_curl, CURLOPT_POSTFIELDSIZE, req->getBody().size());
    curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, req->getBody().c_str());
    curl_easy_setopt(m_curl, CURLOPT_TIMEOUT_MS, timeout_ms);
    curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headers);

    CURLcode res = curl_easy_perform(m_curl);
    if(res != CURLE_OK){
        std::string errstr = curl_easy_strerror(res);
        return HttpResult::ptr(
            new HttpResult(HttpResult::Error::CONNECT_FAIL,boost::shared_ptr<HttpResponse>(),"do request faile by "+errstr));
    }
    curl_slist_free_all(headers);
    long response_code;
    curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &response_code);
    rsp->setStatus((HttpStatus)response_code);
    return HttpResult::ptr(new HttpResult(HttpResult::Error::OK,rsp,"OK"));
}

HttpConnection::HttpConnection(){
    m_curl = curl_easy_init();
    curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L);
    curl_easy_setopt(m_curl, CURLOPT_MAXLIFETIME_CONN, 300L);
    curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_easy_setopt(m_curl, CURLOPT_SSL_VERIFYHOST, 0);
    m_close = false;
	m_request = 0;
    m_createTime = GetCurrentMS();
}

HttpConnection::~HttpConnection(){
    curl_easy_cleanup(m_curl);
}

HttpConnectionPool::ptr HttpConnectionPool::Create(const std::string& uri
                                    , const std::string& v_host
                                    , uint32_t max_size
                                    , uint32_t max_alive_time
                                    , uint32_t max_request){
    return  boost::make_shared<HttpConnectionPool>(uri,v_host,max_size,max_alive_time,max_request);
}

HttpConnectionPool::HttpConnectionPool(const std::string& host
                , const std::string& vhost
                , uint32_t max_size
                , uint32_t max_alive_time
                , uint32_t max_request){
    m_host = host;
    m_vhost = vhost;
    m_maxSize = max_size;
    m_maxAliveTime = max_alive_time;
    m_maxRequest = max_request;
	m_total = 0;
}

HttpConnection::ptr HttpConnectionPool::getConnection(){
    uint64_t now_ms = GetCurrentMS();
    std::vector<HttpConnection*> invalid_conn;
    HttpConnection* conn_ptr = NULL;
    MutexType::Lock lock(m_mutex);
    while(!m_conns.empty()){
        HttpConnection* conn = *(m_conns.begin());
        m_conns.pop_front();
        if(conn->m_close){
            invalid_conn.push_back(conn);
            continue;
        }
        if(conn->m_createTime + m_maxAliveTime < now_ms){
            invalid_conn.push_back(conn);
            continue;
        }
        conn_ptr = conn;
        break;
    }
    lock.unlock();
	for(std::vector<HttpConnection*>::const_iterator i = invalid_conn.begin();i!=invalid_conn.end();++i){
        delete (*i);
    }
    m_total -= invalid_conn.size();

    if(!conn_ptr){
        conn_ptr = new HttpConnection();
        ++m_total;
    }

    return HttpConnection::ptr(conn_ptr,boost::bind(&HttpConnectionPool::ReleasePtr,_1,this));

}

HttpResult::ptr HttpConnectionPool::DoGet(const std::string& url, uint64_t timeout_ms
                            , const std::map<std::string, std::string>& header
                            , const std::string& body){
    return DoRequest(HttpMethod::GET,url,timeout_ms,header,body);
}
HttpResult::ptr HttpConnectionPool::DoPost(const std::string& url, uint64_t timeout_ms
                            , const std::map<std::string, std::string>& header
                            , const std::string& body){
    return DoRequest(HttpMethod::POST,url,timeout_ms,header,body);
}

HttpResult::ptr HttpConnectionPool::DoRequest(HttpMethod method, const std::string& url, uint64_t timeout_ms
                            , const std::map<std::string, std::string>& header
                            , const std::string& body){
    HttpRequest::ptr req = boost::make_shared<HttpRequest>();
    req->setMethod(method);
    req->setBody(body);
    for(std::map<std::string, std::string>::const_iterator i = header.begin();i != header.end(); ++i){
        req->setHeader(i->first,i->second);
    }
    return DoRequest(req,url,timeout_ms);
}

HttpResult::ptr HttpConnectionPool::DoRequest(HttpRequest::ptr req, const std::string& url, uint64_t timeout_ms){
    HttpConnection::ptr conn = getConnection();
    HttpResult::ptr result = conn->DoRequest(req,url,timeout_ms);
    return result;
}

void HttpConnectionPool::ReleasePtr(HttpConnection* conn_ptr, HttpConnectionPool* pool){
    conn_ptr->m_request++;
    if(conn_ptr->m_close
        || (conn_ptr->m_request >= pool->m_maxRequest)
        || (conn_ptr->m_createTime + pool->m_maxAliveTime <= GetCurrentMS())){
        delete conn_ptr;
        --(pool->m_total);
        return;
    }
    MutexType::Lock lock(pool->m_mutex);
    pool->m_conns.push_back(conn_ptr);
}


} // namespace http
} // namespace uesdk
